
package com.whosly.mysql.protocol.packet.util;

import io.netty.buffer.ByteBuf;

import java.nio.charset.Charset;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.Date;

/**
 * netty buffer工具类
 */
public class NettyBufferUtil {
    public static final byte[] FILLER2 = new byte[2];
    public static final byte[] FILLER3 = new byte[3];
    public static final byte[] FILLER5 = new byte[5];
    // writeInt
    public static final void writeUB2(ByteBuf buffer, int i) {
        buffer.writeByte((byte) (i & 0xff));
        buffer.writeByte((byte) (i >>> 8));
    }

    // is writeLongInt
    public static final void writeUB3(ByteBuf buffer, int i) {
        buffer.writeByte((byte) (i & 0xff));
        buffer.writeByte((byte) (i >>> 8));
        buffer.writeByte((byte) (i >>> 16));
    }

    public static final byte[] parseUB3(int l) {
        byte[] result = new byte[3];
        result[0] = (byte) (l & 0xff);
        result[1] = (byte) (l >>> 8);
        result[2] = (byte) (l >>> 16);
        return result;
    }

    /**
     * 设置packetSize到前三位，用于不能提前获取到包长度的场合
     * 调用setByte方法设值避免拷贝
     * @param buffer
     * @param size
     */
    public static final void setPacketSize(ByteBuf buffer, int size) {
        buffer.setByte(0,(byte) (size & 0xff));
        buffer.setByte(1,(byte) (size >>> 8));
        buffer.setByte(2,(byte) (size >>> 16));
    }

    public static final void writeInt(ByteBuf buffer, int i) {
        buffer.writeByte((byte) (i & 0xff));
        buffer.writeByte((byte) (i >>> 8));
        buffer.writeByte((byte) (i >>> 16));
        buffer.writeByte((byte) (i >>> 24));
    }

    public static final void writeFloat(ByteBuf buffer, float f) {
        writeInt(buffer, Float.floatToIntBits(f));
    }

    public static final void writeUB4(ByteBuf buffer, long l) {
        buffer.writeByte((byte) (l & 0xff));
        buffer.writeByte((byte) (l >>> 8));
        buffer.writeByte((byte) (l >>> 16));
        buffer.writeByte((byte) (l >>> 24));
    }

    // writeLongLong
    public static final void writeLong(ByteBuf buffer, long l) {
        buffer.writeByte((byte) (l & 0xff));
        buffer.writeByte((byte) (l >>> 8));
        buffer.writeByte((byte) (l >>> 16));
        buffer.writeByte((byte) (l >>> 24));
        buffer.writeByte((byte) (l >>> 32));
        buffer.writeByte((byte) (l >>> 40));
        buffer.writeByte((byte) (l >>> 48));
        buffer.writeByte((byte) (l >>> 56));
    }

    public static final void writeDouble(ByteBuf buffer, double d) {
        writeLong(buffer, Double.doubleToLongBits(d));
    }

    public static final void writeLength(ByteBuf buffer, long l) {
        if (l < 251) {
            buffer.writeByte((byte) l);
        } else if (l < 0x10000L) {
            buffer.writeByte((byte) 252);
            writeUB2(buffer, (int) l);
        } else if (l < 0x1000000L) {
            buffer.writeByte((byte) 253);
            writeUB3(buffer, (int) l);
        } else {
            buffer.writeByte((byte) 254);
            writeLong(buffer, l);
        }
    }

    public static final void writeWithNull(ByteBuf buffer, byte[] src) {
        buffer.writeBytes(src);
        buffer.writeByte((byte) 0);
    }

    public static final void writeWithNull(ByteBuf buffer, String src, Charset charset) {
        //        Charset cc = Charset.forName("utf8");

        buffer.writeBytes(src.getBytes(charset));
        buffer.writeByte((byte) 0);
    }

    public static final void writeWithLength(ByteBuf buffer, String src) {
        if (src == null) {
            buffer.writeByte(0x00);
        } else {
            writeWithLength(buffer, src.getBytes());
        }
    }

    public static final void writeWithLength(ByteBuf buffer, byte[] src) {
        if (src == null) {
            buffer.writeByte(0x00);
            return;
        }
        writeLength(buffer, src.length);
        buffer.writeBytes(src);
    }

    public static final void writeWithLength(ByteBuf buffer, byte[] src, byte nullValue) {
        if (src == null) {
            buffer.writeByte(nullValue);
        } else {
            writeWithLength(buffer, src);
        }
    }

    public static final void writeDate(ByteBuf buffer, Date value){
        Calendar cal = Calendar.getInstance();
        cal.clear();
        cal.setTime(value);
        int year = cal.get(Calendar.YEAR);
        int month = cal.get(Calendar.MONTH) + 1;
        int day = cal.get(Calendar.DAY_OF_MONTH);
        int hour = cal.get(Calendar.HOUR_OF_DAY);
        int minute = cal.get(Calendar.MINUTE);
        int second = cal.get(Calendar.SECOND);
        int microSecond;
        if (value instanceof Timestamp) {
            // timestamp类型的nanos存有更精确的毫秒
            microSecond = ((Timestamp) value).getNanos() / 1000;
        } else {
            microSecond = cal.get(Calendar.MILLISECOND) * 1000;
        }

        // if year, month, day, hour, minutes, seconds and micro_seconds are all 0, length is 0 and no other field is sent
        if (year == 0 && month == 0 && day == 0
                && hour == 0 && minute == 0 && second == 0 && microSecond == 0) {
            buffer.writeByte(0);
            return;
        }

        if (hour == 0 && minute == 0 && second == 0 && microSecond == 0) {
            int length = 4;
            buffer.writeByte(length);
            writeUB2(buffer, year);
            buffer.writeByte(month);
            buffer.writeByte(day);
            return;
        }

        if (microSecond == 0) {
            int length = 7;
            buffer.writeByte(length);
            writeUB2(buffer, year);
            buffer.writeByte(month);
            buffer.writeByte(day);
            buffer.writeByte(hour);
            buffer.writeByte(minute);
            buffer.writeByte(second);
            return;
        }

        int length = 11;
        buffer.writeByte(length);
        writeUB2(buffer, year);
        buffer.writeByte(month);
        buffer.writeByte(day);
        buffer.writeByte(hour);
        buffer.writeByte(minute);
        buffer.writeByte(second);
        writeUB4(buffer, microSecond);
    }

    public static final void writeTime(ByteBuf buffer, Time value) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(value);
        int hour = cal.get(Calendar.HOUR_OF_DAY);
        int minute = cal.get(Calendar.MINUTE);
        int second = cal.get(Calendar.SECOND);
        int microSecond = cal.get(Calendar.MILLISECOND) * 1000;

        // if days, hours, minutes, seconds and micro_seconds are all 0, length is 0 and no other field is sent
        if (hour == 0 && minute == 0 && second == 0 && microSecond == 0) {
            buffer.writeByte(0);
            return;
        }
        // if micro_seconds is 0, length is 8 and micro_seconds is not sent
        if (microSecond == 0) {
            int length = 8;
            buffer.writeByte(length);
            // is_negative(1) =0
            // days(4) =0
            buffer.readBytes(FILLER5);
            buffer.writeByte(hour);
            buffer.writeByte(minute);
            buffer.writeByte(second);
            return;
        }
        int length = 12;
        buffer.writeByte(length);
        // is_negative(1) =0
        // days(4) =0
        buffer.readBytes(FILLER5);
        buffer.writeByte(hour);
        buffer.writeByte(minute);
        buffer.writeByte(second);
        writeUB4(buffer, microSecond);
    }


    ////////////////////////////////////////////////
    // read
    ////////////////////////////////////////////////

    public static byte readByte(ByteBuf data) {
        return data.readByte();
    }

    public static int readUB3(ByteBuf data) {
        int i = data.readByte() & 0xff;
        i |= (data.readByte() & 0xff) << 8;
        i |= (data.readByte() & 0xff) << 16;
        return i;
    }

    public static long readUB4(ByteBuf data) {
        long l = data.readByte() & 0xff;
        l |= (data.readByte() & 0xff) << 8;
        l |= (data.readByte() & 0xff) << 16;
        l |= (data.readByte() & 0xff) << 24;
        return l;
    }

    public static long readLong(ByteBuf data) {
        long l = (long) (data.readByte() & 0xff);
        l |= (long) (data.readByte() & 0xff) << 8;
        l |= (long) (data.readByte() & 0xff) << 16;
        l |= (long) (data.readByte() & 0xff) << 24;
        l |= (long) (data.readByte() & 0xff) << 32;
        l |= (long) (data.readByte() & 0xff) << 40;
        l |= (long) (data.readByte() & 0xff) << 48;
        l |= (long) (data.readByte() & 0xff) << 56;
        return l;
    }



    /**
     * 获取length var的length前缀长度
     * @param length
     * @return
     */
    public static final int getLengthVarPrefixSize(long length) {
        if (length < 251) {
            return 1;
        } else if (length < 0x10000L) {
            return 3;
        } else if (length < 0x1000000L) {
            return 4;
        } else {
            return 9;
        }
    }

    /**
     * 获取length var编码后的长度
     * @param src
     * @return
     */
    public static final int getLengthVarSize(byte[] src) {
        if (src == null) {
            return 1;
        }
        return getLengthVarPrefixSize(src.length) + src.length;
    }

    @Deprecated
    public static final int getLength(long length) {
        return getLengthVarPrefixSize(length);
    }

    @Deprecated
    public static final int getLength(byte[] src) {
        return getLengthVarSize(src);
    }
}